import pandas as pd
import ping3
import tkinter as tk
from tkinter import ttk, filedialog
import threading
from tkinter import messagebox
import concurrent.futures
import openpyxl
from openpyxl.styles import Font, Alignment
import datetime
import io
import schedule
import time
import winsound
from PIL import Image, ImageDraw
import pystray
import os
import json

class NetworkCheckerApp:
    def __init__(self):
        self.root = tk.Tk()
        self.root.title("网络连通性检查工具 v1.0")
        self.root.geometry("800x600")  # 加大窗口
        # 设置样式
        style = ttk.Style()
        style.theme_use('clam')  # 或 'alt', 'default', 'classic'
        # 设置Treeview样式
        style.map('Treeview', 
            foreground=[('selected', '#000000')],
            background=[('selected', '#CCE5FF')]
        )
        # 主框架
        main_frame = ttk.Frame(self.root, padding="10")
        main_frame.pack(fill=tk.BOTH, expand=True)
        # 顶部信息框架
        info_frame = ttk.Frame(main_frame)
        info_frame.pack(fill=tk.X, pady=(0, 10))
        # 添加统计信息标签
        self.status_label = ttk.Label(info_frame, text="就绪")
        self.status_label.pack(side=tk.LEFT)
        # 进度条
        self.progress = ttk.Progressbar(info_frame, mode='determinate')
        self.progress.pack(side=tk.RIGHT, fill=tk.X, expand=True, padx=10)
        # 按钮框架
        btn_frame = ttk.Frame(main_frame)
        btn_frame.pack(fill=tk.X, pady=(0, 10))
        # 美化按钮
        self.load_btn = ttk.Button(btn_frame, text="📂 导入Excel", command=self.load_excel)
        self.check_btn = ttk.Button(btn_frame, text="▶ 开始检测", command=self.start_check)
        self.stop_btn = ttk.Button(btn_frame, text="⏹ 停止检测", command=self.stop_check, state='disabled')
        self.export_btn = ttk.Button(btn_frame, text="💾 导出结果", command=self.export_result)
        self.schedule_btn = ttk.Button(btn_frame, text="⏰ 定时检测", command=self.show_schedule_dialog)
        # 声音设置
        self.alarm_enabled = tk.BooleanVar(value=True)
        self.sound_file = None  # 自定义声音文件路径
        # 在按钮框架中添加报警设置
        alarm_frame = ttk.Frame(btn_frame)
        alarm_frame.pack(side=tk.LEFT, padx=5)
        alarm_btn = ttk.Checkbutton(alarm_frame, text="声音报警", variable=self.alarm_enabled)
        alarm_btn.pack(side=tk.LEFT)
        ttk.Button(alarm_frame, text="选择声音", command=self.choose_sound).pack(side=tk.LEFT, padx=2)
        # 表格
        self.tree = ttk.Treeview(main_frame, 
            columns=('序号', 'IP', '备注', '状态', '延迟', '检测时间'), 
            show='headings')
        # 设置表头
        self.tree.heading('序号', text='序号')
        self.tree.heading('IP', text='IP地址')
        self.tree.heading('备注', text='备注')
        self.tree.heading('状态', text='状态')
        self.tree.heading('延迟', text='延迟')
        self.tree.heading('检测时间', text='检测时间')
        # 设置列宽和对齐
        self.tree.column('序号', width=50, anchor='center')
        self.tree.column('IP', width=150, anchor='center')
        self.tree.column('备注', width=200, anchor='w')
        self.tree.column('状态', width=100, anchor='center')
        self.tree.column('延迟', width=100, anchor='center')
        self.tree.column('检测时间', width=150, anchor='center')
        # 添加滚动条
        scrollbar = ttk.Scrollbar(main_frame, orient=tk.VERTICAL, command=self.tree.yview)
        self.tree.configure(yscrollcommand=scrollbar.set)
        # 布局
        self.load_btn.pack(side=tk.LEFT, padx=5)
        self.check_btn.pack(side=tk.LEFT, padx=5)
        self.stop_btn.pack(side=tk.LEFT, padx=5)
        self.export_btn.pack(side=tk.LEFT, padx=5)
        self.schedule_btn.pack(side=tk.LEFT, padx=5)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
        self.tree.pack(fill=tk.BOTH, expand=True)
        # 版权信息框架
        copyright_frame = ttk.Frame(main_frame)
        copyright_frame.pack(fill=tk.X, side=tk.BOTTOM, pady=(5, 0))
        version_label = ttk.Label(copyright_frame, 
            text="版本: v1.0", 
            font=('微软雅黑', 8))
        version_label.pack(side=tk.LEFT)
        copyright_label = ttk.Label(copyright_frame, 
            text="© 2024 甘肃传德源科技工程有限公司", 
            font=('微软雅黑', 8))
        copyright_label.pack(side=tk.RIGHT)
        # 添加停止标志
        self.is_checking = False
        # 添加右键菜单
        self.context_menu = tk.Menu(self.root, tearoff=0)
        self.context_menu.add_command(label="重新检测选中项", command=self.recheck_selected)
        self.context_menu.add_command(label="复制IP地址", command=self.copy_ip)
        self.context_menu.add_separator()
        self.context_menu.add_command(label="清除所有结果", command=self.clear_results)
        self.tree.bind("<Button-3>", self.show_context_menu)
        # 添加定时任务状态标签
        self.schedule_label = ttk.Label(info_frame, text="")
        self.schedule_label.pack(side=tk.LEFT, padx=10)
        # 定时任务变量
        self.scheduled_time = None
        self.schedule_thread = None
        # 添加系统托盘相关变量
        self.icon = None
        self.create_tray_icon()
        # 绑定窗口关闭事件
        self.root.protocol('WM_DELETE_WINDOW', self.hide_window)
        # 声音设置
        self.enable_sound = tk.BooleanVar(value=True)
        # 在菜单中添加声音设置
        self.context_menu.add_separator()
        self.context_menu.add_checkbutton(
            label="启用声音提醒", 
            variable=self.enable_sound)
        # 添加线程池配置
        self.max_workers = min(32, os.cpu_count() * 2)  # 根据CPU核心数设置最大线程数
        self.batch_size = 50  # 每批处理的IP数量
        # 加载设置
        self.load_settings_from_file()
        # 在程序退出时保存设置
        self.root.protocol('WM_DELETE_WINDOW', self.on_closing)
    def load_excel(self):
        try:
            file_path = filedialog.askopenfilename(
                title="选择Excel文件",
                filetypes=[("Excel文件", "*.xlsx;*.xls"), ("所有文件", "*.*")]
            )
            if not file_path:
                return
            self.df = pd.read_excel(file_path)
            # 检查列名，支持多种可能的列名格式
            ip_columns = ['IP address', 'IP地址', 'IP', 'ip', 'ip地址', 'IP_address']
            remark_columns = ['备注', '说明', 'remark', 'Remark', 'Comment', 'comment']
            # 查找IP地址列
            ip_col = None
            for col in ip_columns:
                if col in self.df.columns:
                    ip_col = col
                    break
            # 查找备注列
            remark_col = None
            for col in remark_columns:
                if col in self.df.columns:
                    remark_col = col
                    break
            if ip_col is None or remark_col is None:
                tk.messagebox.showerror(
                    "格式错误",
                    f"Excel文件格式错误！\n\n"
                    f"请确保Excel文件包含以下列：\n"
                    f"1. IP地址列（可用名称：{', '.join(ip_columns)}）\n"
                    f"2. 备注列（可用名称：{', '.join(remark_columns)}）\n\n"
                    f"当前文件的列：{', '.join(self.df.columns)}"
                )
                return
            # 验证IP地址格式
            invalid_ips = []
            for idx, row in self.df.iterrows():
                ip = str(row[ip_col]).strip()
                if not self.is_valid_ip(ip):
                    invalid_ips.append(f"行 {idx+2}: {ip}")
            if invalid_ips:
                tk.messagebox.showwarning(
                    "IP地址格式错误",
                    f"以下IP地址格式无效：\n\n{chr(10).join(invalid_ips[:5])}"
                    f"\n{'...' if len(invalid_ips) > 5 else ''}"
                )
            # 清空现有数据
            for i in self.tree.get_children():
                self.tree.delete(i)
            # 加载数据到界面，添加序号
            for idx, row in enumerate(self.df.iterrows(), 1):
                _, row_data = row
                item = self.tree.insert('', 'end', values=(
                    idx,  # 添加序号
                    row_data[ip_col],  # IP地址
                    row_data[remark_col],  # 备注
                    '待检测',  # 状态
                    '',  # 延迟
                    ''  # 检测时间
                ))
                self.tree.tag_configure(item, foreground='gray')
                self.tree.item(item, tags=(item,))
            # 更新统计信息
            self.update_statistics()
        except Exception as e:
            tk.messagebox.showerror("错误", f"读取Excel文件失败：\n{str(e)}")
    def is_valid_ip(self, ip):
        """验证IP地址格式"""
        try:
            parts = ip.split('.')
            return len(parts) == 4 and all(0 <= int(part) <= 255 for part in parts)
        except:
            return False
    def check_ip(self, item):
        """检测单个IP的连通性"""
        if not self.is_checking:
            return
        full_ip = self.tree.item(item)['values'][1]
        # 提取实际IP地址（去掉序号部分）
        ip = full_ip.split('. ', 1)[1] if '. ' in full_ip else full_ip
        try:
            delay = ping3.ping(ip, timeout=2)
            status = '在线' if delay else '离线'
            return (item, full_ip, self.tree.item(item)['values'][2], status, delay)
        except Exception as e:
            return (item, full_ip, self.tree.item(item)['values'][2], f'检测失败: {str(e)}', None)
    def update_ui(self, item, status, delay, current_count, total_count):
        """安全地更新UI"""
        try:
            # 获取之前的状态
            prev_status = self.tree.item(item)['values'][2]
            # 设置行颜色
            if '在线' in status:
                self.tree.tag_configure(item, foreground='green')
                if prev_status != '待检测' and '离线' in prev_status:
                    self.play_sound('online')
            elif '离线' in status:
                self.tree.tag_configure(item, foreground='red')
                if prev_status != '待检测' and '在线' in prev_status:
                    self.play_sound('offline')
            else:  # 检测失败
                self.tree.tag_configure(item, foreground='orange')
            # 添加检测时间
            check_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
            self.tree.item(item, values=(
                self.tree.item(item)['values'][0],  # 序号
                self.tree.item(item)['values'][1],  # IP地址
                self.tree.item(item)['values'][2],  # 备注
                status,  # 更新状态
                f'{delay:.2f}ms' if delay else '',  # 延迟
                check_time  # 检测时间
            ), tags=(item,))
            # 自动滚动到当前检测项
            self.tree.see(item)
            self.progress['value'] = (current_count / total_count) * 100
            self.root.update_idletasks()
            # 检测完成时处理
            if current_count == total_count:
                self.play_sound('complete')
                # 获取所有离线IP
                offline_items = [item for item in self.tree.get_children()
                               if '离线' in self.tree.item(item)['values'][2]]
                # 如果有离线IP，显示报警窗口
                if offline_items:
                    self.root.after(0, lambda: self.show_offline_alert(offline_items))
        except Exception as e:
            print(f"UI更新错误: {str(e)}")
    def start_check(self):
        """执行网络检测"""
        def check_thread():
            self.is_checking = True
            items = self.tree.get_children()
            total = len(items)
            current = 0
            self.check_btn.config(state='disabled')
            self.stop_btn.config(state='normal')
            self.load_btn.config(state='disabled')
            self.export_btn.config(state='disabled')
            # 重置进度条
            self.progress['value'] = 0
            try:
                # 分批处理
                for i in range(0, len(items), self.batch_size):
                    if not self.is_checking:
                        break
                    batch_items = items[i:i + self.batch_size]
                    # 使用线程池并发检测当前批次
                    with concurrent.futures.ThreadPoolExecutor(max_workers=self.max_workers) as executor:
                        futures = [executor.submit(self.check_ip, item) for item in batch_items]
                        for future in concurrent.futures.as_completed(futures):
                            if not self.is_checking:
                                executor.shutdown(wait=False)
                                break
                            try:
                                result = future.result()
                                if result:
                                    item, ip, remark, status, delay = result
                                    current += 1
                                    self.root.after(0, self.update_ui, item, status, delay, current, total)
                                    self.root.after(0, lambda: self.root.title(
                                        f"网络连通性检查工具 - 进度: {current}/{total}"))
                            except Exception as e:
                                print(f"检测错误: {str(e)}")
                                continue
                    # 每批处理完后暂停一小段时间，避免系统负载过高
                    time.sleep(0.1)
            finally:
                # 恢复按钮状态
                self.root.after(0, lambda: self.check_btn.config(state='normal'))
                self.root.after(0, lambda: self.stop_btn.config(state='disabled'))
                self.root.after(0, lambda: self.load_btn.config(state='normal'))
                self.root.after(0, lambda: self.export_btn.config(state='normal'))
                self.root.after(0, lambda: self.root.title("网络连通性检查工具"))
                self.root.after(0, self.update_statistics)
                # 移除检测完成的提示对话框
                if self.is_checking:  # 正常完成
                    # 获取所有离线IP
                    offline_items = [item for item in self.tree.get_children()
                                   if '离线' in self.tree.item(item)['values'][2]]
                    # 如果有离线IP，显示报警窗口
                    if offline_items:
                        self.root.after(0, lambda: self.show_offline_alert(offline_items))
                    else:
                        # 如果没有离线IP，播放完成提示音
                        self.play_sound('complete')
                else:  # 检测被停止
                    self.root.after(0, lambda: messagebox.showinfo("已停止", "网络检测已停止！"))
                self.is_checking = False
        # 启动新线程执行检测
        threading.Thread(target=check_thread, daemon=True).start()
    def stop_check(self):
        """停止网络检测"""
        self.is_checking = False
        self.stop_btn.config(state='disabled')
    def export_result(self):
        """导出结果到Excel"""
        try:
            output_path = filedialog.asksaveasfilename(
                title="导出结果",
                defaultextension=".xlsx",
                filetypes=[("Excel文件", "*.xlsx"), ("所有文件", "*.*")]
            )
            if not output_path:
                return
            # 获取所有数据并添加序号
            data = []
            for item in self.tree.get_children():
                values = self.tree.item(item)['values']
                # 从IP地址中提取实际IP（去掉序号）
                full_ip = values[1]
                ip = full_ip.split('. ', 1)[1] if '. ' in full_ip else full_ip
                data.append([
                    values[0],  # 序号
                    ip,  # 实际IP地址
                    values[2],  # 备注
                    values[3],  # 状态
                    values[4],  # 延迟
                    values[5]  # 检测时间
                ])
            df = pd.DataFrame(data, columns=['序号', 'IP地址', '备注', '状态', '延迟', '检测时间'])
            # 创建DataFrame并导出
            with pd.ExcelWriter(output_path, engine='openpyxl') as writer:
                df.to_excel(writer, index=False, sheet_name='检测结果')
                # 获取工作表
                worksheet = writer.sheets['检测结果']
                # 调整列宽
                worksheet.column_dimensions['A'].width = 15  # IP地址
                worksheet.column_dimensions['B'].width = 20  # 备注
                worksheet.column_dimensions['C'].width = 12  # 状态
                worksheet.column_dimensions['D'].width = 12  # 延迟
                worksheet.column_dimensions['E'].width = 15  # 检测时间
                worksheet.column_dimensions['F'].width = 20  # 检测时间列
                # 设置标题行格式
                for cell in worksheet[1]:
                    cell.font = openpyxl.styles.Font(bold=True)
                    cell.alignment = openpyxl.styles.Alignment(horizontal='center')
                # 设置所有单元格居中对齐（除备注外）
                for row in worksheet.iter_rows(min_row=2):
                    for idx, cell in enumerate(row):
                        if idx != 1:  # 不是备注列
                            cell.alignment = openpyxl.styles.Alignment(horizontal='center')
            messagebox.showinfo("成功", "结果已成功导出！")
        except Exception as e:
            messagebox.showerror("错误", f"导出失败：\n{str(e)}")
    def update_statistics(self):
        """更新统计信息"""
        total = len(self.tree.get_children())
        online = sum(1 for item in self.tree.get_children() 
                    if '在线' in self.tree.item(item)['values'][3])  # 确保检查状态列
        offline = sum(1 for item in self.tree.get_children() 
                     if '离线' in self.tree.item(item)['values'][3])  # 确保检查状态列
        failed = sum(1 for item in self.tree.get_children() 
                    if '失败' in self.tree.item(item)['values'][3])  # 确保检查状态列
        stats = f"总计: {total} | 在线: {online} | 离线: {offline} | 失败: {failed}"
        self.status_label.config(text=stats)
    def show_context_menu(self, event):
        """显示右键菜单"""
        item = self.tree.identify_row(event.y)
        if item:
            self.tree.selection_set(item)
            self.context_menu.post(event.x_root, event.y_root)
    def copy_ip(self):
        """复制选中的IP地址"""
        selected = self.tree.selection()
        if selected:
            ip = self.tree.item(selected[0])['values'][1]
            self.root.clipboard_clear()
            self.root.clipboard_append(ip)
    def recheck_selected(self):
        """重新检测选中的IP"""
        selected = self.tree.selection()
        if not selected:
            return
        def check_selected_thread():
            self.is_checking = True
            total = len(selected)
            current = 0
            self.root.after(0, lambda: self.check_btn.config(state='disabled'))
            self.root.after(0, lambda: self.stop_btn.config(state='normal'))
            try:
                # 分批处理选中项
                for i in range(0, len(selected), self.batch_size):
                    if not self.is_checking:
                        break
                    batch_items = selected[i:i + self.batch_size]
                    with concurrent.futures.ThreadPoolExecutor(max_workers=self.max_workers) as executor:
                        futures = [executor.submit(self.check_ip, item) for item in batch_items]
                        for future in concurrent.futures.as_completed(futures):
                            if not self.is_checking:
                                executor.shutdown(wait=False)
                                break
                            try:
                                result = future.result()
                                if result:
                                    item, ip, remark, status, delay = result
                                    current += 1
                                    self.root.after(0, self.update_ui, item, status, delay, current, total)
                            except Exception as e:
                                print(f"重新检测错误: {str(e)}")
                                continue
                    time.sleep(0.1)
            finally:
                self.root.after(0, lambda: self.check_btn.config(state='normal'))
                self.root.after(0, lambda: self.stop_btn.config(state='disabled'))
                self.root.after(0, self.update_statistics)
                self.is_checking = False
        threading.Thread(target=check_selected_thread, daemon=True).start()
    def clear_results(self):
        """清除所有检测结果"""
        for item in self.tree.get_children():
            self.tree.tag_configure(item, foreground='gray')  # 重置为灰色
            self.tree.item(item, values=(
                self.tree.item(item)['values'][0],  # 序号
                self.tree.item(item)['values'][1],  # IP地址
                self.tree.item(item)['values'][2],  # 备注
                '待检测',  # 状态
                '',  # 延迟
                ''  # 检测时间
            ), tags=(item,))
        self.update_statistics()
        self.root.title("网络连通性检查工具")
    def show_schedule_dialog(self):
        """显示定时设置对话框"""
        dialog = tk.Toplevel(self.root)
        dialog.title("定时检测设置")
        dialog.geometry("400x300")  # 减小窗口高度
        dialog.transient(self.root)
        # 主框架
        main_frame = ttk.Frame(dialog, padding="10")
        main_frame.pack(fill=tk.BOTH, expand=True)
        # 时间设置框架
        time_frame = ttk.LabelFrame(main_frame, text="检测时间", padding="10")
        time_frame.pack(fill=tk.X, pady=5)
        # 获取当前日期时间
        now = datetime.datetime.now()
        # 年月日设置
        date_frame = ttk.Frame(time_frame)
        date_frame.pack(fill=tk.X, pady=5)
        year_var = tk.StringVar(value=str(now.year))
        month_var = tk.StringVar(value=str(now.month))
        day_var = tk.StringVar(value=str(now.day))
        ttk.Label(date_frame, text="年:").pack(side=tk.LEFT)
        year_spin = ttk.Spinbox(date_frame, from_=2024, to=2100, width=6, textvariable=year_var)
        year_spin.pack(side=tk.LEFT, padx=5)
        ttk.Label(date_frame, text="月:").pack(side=tk.LEFT)
        month_spin = ttk.Spinbox(date_frame, from_=1, to=12, width=4, textvariable=month_var)
        month_spin.pack(side=tk.LEFT, padx=5)
        ttk.Label(date_frame, text="日:").pack(side=tk.LEFT)
        day_spin = ttk.Spinbox(date_frame, from_=1, to=31, width=4, textvariable=day_var)
        day_spin.pack(side=tk.LEFT, padx=5)
        # 时分设置
        time_frame2 = ttk.Frame(time_frame)
        time_frame2.pack(fill=tk.X, pady=5)
        hour_var = tk.StringVar(value=str(now.hour))
        minute_var = tk.StringVar(value=str(now.minute))
        ttk.Label(time_frame2, text="时:").pack(side=tk.LEFT)
        hour_spin = ttk.Spinbox(time_frame2, from_=0, to=23, width=4, textvariable=hour_var)
        hour_spin.pack(side=tk.LEFT, padx=5)
        ttk.Label(time_frame2, text="分:").pack(side=tk.LEFT)
        minute_spin = ttk.Spinbox(time_frame2, from_=0, to=59, width=4, textvariable=minute_var)
        minute_spin.pack(side=tk.LEFT, padx=5)
        # 倒计时标签
        self.countdown_label = ttk.Label(main_frame, text="", font=('微软雅黑', 10))
        self.countdown_label.pack(pady=(10, 0))
        # 检测模式选择
        mode_frame = ttk.Frame(time_frame)
        mode_frame.pack(fill=tk.X, pady=5)
        mode_var = tk.StringVar(value="once")
        ttk.Radiobutton(mode_frame, text="单次检测", 
                       variable=mode_var, value="once").pack(side=tk.LEFT)
        ttk.Radiobutton(mode_frame, text="定时循环", 
                       variable=mode_var, value="interval").pack(side=tk.LEFT)
        # 循环间隔设置
        interval_frame = ttk.Frame(time_frame)
        interval_frame.pack(fill=tk.X, pady=5)
        interval_var = tk.StringVar(value="10")
        ttk.Label(interval_frame, text="循环间隔(分钟):").pack(side=tk.LEFT)
        interval_spin = ttk.Spinbox(interval_frame, from_=1, to=1440, 
                                  width=5, textvariable=interval_var)
        interval_spin.pack(side=tk.LEFT, padx=5)
        # 按钮框架
        btn_frame = ttk.Frame(main_frame)
        btn_frame.pack(fill=tk.X, pady=10)
        save_settings_with_args = lambda: self.save_settings(
            dialog, year_var, month_var, day_var, 
            hour_var, minute_var, mode_var, interval_var
        )
        ttk.Button(btn_frame, text="开始定时", command=save_settings_with_args).pack(side=tk.LEFT, padx=5)
        ttk.Button(btn_frame, text="停止定时", command=self.stop_check, state='disabled').pack(side=tk.LEFT, padx=5)
        ttk.Button(btn_frame, text="取消", command=dialog.destroy).pack(side=tk.RIGHT, padx=5)
    def schedule_check(self, schedule_time, mode="once", interval=60):
        """设置定时检测
        mode: 'once' 单次检测, 'interval' 循环检测
        interval: 循环间隔（分钟）
        """
        self.scheduled_time = schedule_time
        self.schedule_mode = mode
        self.schedule_interval = interval
        self.start_countdown(schedule_time)  # 启动倒计时
        def schedule_task():
            while True:
                schedule.run_pending()
                time.sleep(1)  # 每秒检查一次
        # 清除现有的定时任务
        schedule.clear()
        if mode == "once":
            # 单次检测
            schedule.every().day.at(schedule_time.strftime("%H:%M")).do(
                self.run_scheduled_check)
            status = f"定时检测: {schedule_time.strftime('%Y-%m-%d %H:%M')}"
        else:
            # 循环检测
            def run_interval_check():
                self.run_scheduled_check()
                self.update_countdown(self.schedule_interval * 60)  # 更新倒计时
                return schedule.CancelJob  # 取消当前任务
            schedule.every(interval).minutes.do(run_interval_check)
            status = f"循环检测: 每{interval}分钟"
        # 更新状态标签
        self.schedule_label.config(text=status)
        # 启动定时任务线程
        if self.schedule_thread is None or not self.schedule_thread.is_alive():
            self.schedule_thread = threading.Thread(
                target=schedule_task, daemon=True)
            self.schedule_thread.start()
    def start_countdown(self, schedule_time):
        """启动倒计时"""
        now = datetime.datetime.now()
        countdown_seconds = (schedule_time - now).total_seconds()
        if countdown_seconds > 0:
            self.update_countdown(countdown_seconds)
    def update_countdown(self, seconds):
        """更新倒计时标签"""
        if seconds > 0:
            minutes, seconds = divmod(int(seconds), 60)
            if self.countdown_label.winfo_exists():  # 检查标签是否仍然存在
                self.countdown_label.config(text=f"倒计时: {minutes} 分 {seconds} 秒")
            self.root.after(1000, self.update_countdown, seconds - 1)
        else:
            if self.countdown_label.winfo_exists():  # 检查标签是否仍然存在
                self.countdown_label.config(text="倒计时结束！")
    def run_scheduled_check(self):
        """执行定时检测"""
        try:
            self.start_check()
            # 等待检测完成
            while self.is_checking:
                time.sleep(1)
            # 获取离线的IP并报警
            offline_items = [item for item in self.tree.get_children()
                           if '离线' in self.tree.item(item)['values'][3]]  # 确保检查状态列
            if offline_items:
                self.root.after(0, lambda: self.show_offline_alert(offline_items))
        except Exception as e:
            print(f"定时检测错误: {str(e)}")
    def show_offline_alert(self, offline_items):
        """显示离线IP报警窗口"""
        # 如果已经存在报警窗口，先关闭它
        if hasattr(self, 'alert_window') and self.alert_window is not None:
            try:
                self.alert_window.destroy()
            except:
                pass
        # 创建新的报警窗口
        self.alert_window = tk.Toplevel(self.root)
        alert = self.alert_window  # 使用更短的变量名
        alert.title("⚠ 发现离线IP")
        alert.geometry("400x300")
        alert.transient(self.root)
        alert.grab_set()  # 设置为模态窗口
        alert.focus_force()
        # 用于控制声音播放的标志
        self.playing_alarm = True
        # 在新线程中播放声音
        def play_alarm_sound():
            while getattr(self, 'playing_alarm', False) and self.alarm_enabled.get():
                try:
                    if self.sound_file and os.path.exists(self.sound_file):
                        winsound.PlaySound(self.sound_file, winsound.SND_FILENAME)
                    else:
                        winsound.PlaySound('SystemHand', winsound.SND_ALIAS)
                    time.sleep(0.5)
                except Exception as e:
                    print(f"播放声音失败: {str(e)}")
                    break
        # 启动声音播放线程
        self.sound_thread = threading.Thread(target=play_alarm_sound, daemon=True)
        self.sound_thread.start()
        # 主框架
        main_frame = ttk.Frame(alert, padding="10")
        main_frame.pack(fill=tk.BOTH, expand=True)
        # 警告信息
        warning_frame = ttk.Frame(main_frame)
        warning_frame.pack(fill=tk.X, pady=(0, 10))
        warning_label = ttk.Label(
            warning_frame, 
            text=f"⚠ 警告：发现 {len(offline_items)} 个离线IP",
            font=('微软雅黑', 12, 'bold'),
            foreground='red'
        )
        warning_label.pack(side=tk.LEFT)
        # 离线IP列表（使用Frame包装以确保正确销毁）
        list_container = ttk.Frame(main_frame)
        list_container.pack(fill=tk.BOTH, expand=True)
        list_frame = ttk.LabelFrame(list_container, text="离线IP列表", padding="5")
        list_frame.pack(fill=tk.BOTH, expand=True)
        # 创建表格
        tree = ttk.Treeview(list_frame, 
            columns=('IP', '备注', '检测时间'), 
            show='headings', 
            height=8
        )
        tree.heading('IP', text='IP地址')
        tree.heading('备注', text='备注')
        tree.heading('检测时间', text='检测时间')
        tree.column('IP', width=150)
        tree.column('备注', width=200)
        tree.column('检测时间', width=150)
        # 添加离线IP数据
        for item in offline_items:
            values = self.tree.item(item)['values']
            tree.insert('', 'end', values=(
                values[1],  # IP
                values[2],  # 备注
                values[5]   # 检测时间
            ))
        # 添加滚动条
        scrollbar = ttk.Scrollbar(list_frame, orient=tk.VERTICAL, command=tree.yview)
        tree.configure(yscrollcommand=scrollbar.set)
        # 布局
        tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
        # 按钮框架
        btn_frame = ttk.Frame(main_frame)
        btn_frame.pack(fill=tk.X, pady=(10, 0))
        def stop_alert():
            """停止报警并关闭窗口"""
            try:
                # 停止声音播放
                self.playing_alarm = False  # 停止声音播放
                # 关闭窗口
                self.alert_window.destroy()
                self.alert_window = None
            except Exception as e:
                print(f"关闭报警窗口错误: {str(e)}")
        # 确认按钮
        ttk.Button(btn_frame, text="确认", command=stop_alert).pack(side=tk.RIGHT)
        # 确保窗口关闭时停止声音和闪烁
        alert.protocol("WM_DELETE_WINDOW", stop_alert)
    def choose_sound(self):
        """选择自定义报警声音"""
        file_path = filedialog.askopenfilename(
            title="选择声音文件",
            filetypes=[
                ("声音文件", "*.wav"),
                ("所有文件", "*.*")
            ]
        )
        if file_path:
            try:
                # 测试播放声音
                winsound.PlaySound(file_path, winsound.SND_FILENAME)
                self.sound_file = file_path
                messagebox.showinfo("成功", "声音设置已保存！")
            except Exception as e:
                messagebox.showerror("错误", f"无法播放所选声音文件：\n{str(e)}")
                self.sound_file = None
    def save_settings(self, dialog, year_var, month_var, day_var, 
                     hour_var, minute_var, mode_var, interval_var):
        """保存定时设置"""
        try:
            # 验证日期时间
            year = int(year_var.get())
            month = int(month_var.get())
            day = int(day_var.get())
            hour = int(hour_var.get())
            minute = int(minute_var.get())
            # 验证日期是否有效
            schedule_time = datetime.datetime(year, month, day, hour, minute)
            # 检查是否是过去的时间
            if schedule_time < datetime.datetime.now():
                raise ValueError("不能设置过去的时间")
            # 获取检测模式和间隔
            mode = mode_var.get()
            interval = int(interval_var.get())
            self.schedule_check(schedule_time, mode, interval)
            dialog.destroy()
            if mode == "once":
                msg = f"已设置定时检测时间: {schedule_time.strftime('%Y-%m-%d %H:%M')}"
            else:
                msg = f"已设置循环检测: 每{interval}分钟检测一次"
            messagebox.showinfo("成功", msg)
        except ValueError as e:
            messagebox.showerror("错误", str(e))
        except Exception as e:
            messagebox.showerror("错误", f"设置失败: {str(e)}")
    def create_tray_icon(self):
        """创建系统托盘图标"""
        # 创建图标
        image = Image.new('RGB', (64, 64), color='white')
        dc = ImageDraw.Draw(image)
        dc.rectangle([16, 16, 48, 48], fill='green')
        # 创建菜单
        menu = (
            pystray.MenuItem("显示主窗口", self.show_window),
            pystray.MenuItem("开始检测", self.start_check),
            pystray.MenuItem("停止检测", self.stop_check),
            pystray.Menu.SEPARATOR,
            pystray.MenuItem("退出", self.quit_app)
        )
        self.icon = pystray.Icon(
            "network_checker",
            image,
            "网络连通性检查工具",
            menu
        )
        # 在新线程中运行图标
        threading.Thread(target=self.icon.run, daemon=True).start()
    def show_window(self, icon=None):
        """显示主窗口"""
        self.root.deiconify()
        self.root.state('normal')
        self.root.lift()
    def hide_window(self):
        """处理窗口关闭事件"""
        if messagebox.askyesno("确认", "是否最小化到系统托盘？\n选择'否'将直接退出程序"):
            self.root.withdraw()
            if self.icon:
                self.icon.notify(
                    "程序已最小化到系统托盘",
                    "网络连通性检查工具"
                )
        else:
            self.quit_app()
    def quit_app(self, icon=None):
        """退出应用"""
        if messagebox.askyesno("确认退出", "确定要退出程序吗？"):
            if self.icon:
                self.icon.stop()
            self.root.quit()
    def play_sound(self, sound_type='offline'):
        """播放提示音"""
        if not self.alarm_enabled.get():
            return
        try:
            if sound_type == 'offline':
                # 离线声音提示由show_offline_alert处理
                pass
            elif sound_type == 'online':
                # 恢复在线提示音
                winsound.PlaySound('SystemAsterisk', winsound.SND_ALIAS)
            elif sound_type == 'complete':
                # 检测完成提示音
                winsound.PlaySound('SystemExclamation', winsound.SND_ALIAS)
        except Exception as e:
            print(f"播放声音失败: {str(e)}")
    def save_settings_to_file(self):
        """保存设置到文件"""
        settings = {
            'alarm_enabled': self.alarm_enabled.get(),
            'sound_file': self.sound_file
        }
        try:
            with open('settings.json', 'w') as f:
                json.dump(settings, f)
        except Exception as e:
            print(f"保存设置失败: {str(e)}")
    def load_settings_from_file(self):
        """从文件加载设置"""
        try:
            if os.path.exists('settings.json'):
                with open('settings.json', 'r') as f:
                    settings = json.load(f)
                self.alarm_enabled.set(settings.get('alarm_enabled', True))
                self.sound_file = settings.get('sound_file')
        except Exception as e:
            print(f"加载设置失败: {str(e)}")
    def on_closing(self):
        """程序关闭时的处理"""
        self.save_settings_to_file()
        self.hide_window()
    def stop_schedule(self):
        """停止定时检测"""
        schedule.clear()
        self.scheduled_time = None
        self.schedule_label.config(text="")
        self.countdown_label.config(text="")  # 清空倒计时标签
        messagebox.showinfo("提示", "已停止定时检测")

if __name__ == "__main__":
    app = NetworkCheckerApp()
    app.root.mainloop() 